/************************************************************************/
/* CharlieCube USB:                                                     */
/* An open-source RGB LED cube firmware with USB connectivity.          */
/*                                                                      */
/* CharlieUSB.c                                                         */
/*                                                                      */
/* Will Robertson <aliask@gmail.com>                                    */
/*                                                                      */
/* Copyright (c) 2012 Will Robertson                                    */
/*                                                                      */
/* Permission is hereby granted, free of charge, to any person          */
/* obtaining a copy of this software and associated documentation       */
/* files (the "Software"), to deal in the Software without restriction, */
/* including without limitation the rights to use, copy, modify, merge, */
/* publish, distribute, sublicense, and/or sell copies of the Software, */
/* and to permit persons to whom the Software is furnished to do so,    */
/* subject to the following conditions:                                 */
/*                                                                      */
/* The above copyright notice and this permission notice shall be       */
/* included in all copies or substantial portions of the Software.      */
/*                                                                      */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,      */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF   */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND                */
/* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS  */
/* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN   */
/* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN    */
/* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE     */
/* SOFTWARE.                                                            */
/*                                                                      */
/************************************************************************/

#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>  /* for sei() */
#include <util/delay.h>     /* for _delay_ms() */
#include <avr/eeprom.h>		/* stores preconfigured animations */
#include <stdlib.h>
#include <avr/pgmspace.h>   /* required by usbdrv.h */
#include "usbdrv/usbdrv.h"

#include "led.h"

PROGMEM const char usbHidReportDescriptor[22] = {    /* USB report descriptor */
    0x06, 0x00, 0xff,              // USAGE_PAGE (Generic Desktop)
    0x09, 0x01,                    // USAGE (Vendor Usage 1)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //   LOGICAL_MAXIMUM (255)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 0x80,                    //   REPORT_COUNT (128)
    0x09, 0x00,                    //   USAGE (Undefined)
    0xb2, 0x02, 0x01,              //   FEATURE (Data,Var,Abs,Buf)
    0xc0                           // END_COLLECTION
};

/* Since we define only one feature report, we don't use report-IDs (which
 * would be the first byte of the report). The entire report consists of 128
 * opaque data bytes.
 */

/* The following variables store the status of the current data transfer */
static uchar currentAddress;
static uchar bytesRemaining;

/* ------------------------------------------------------------------------- */

/* usbFunctionRead() is called when the host requests a chunk of data from
 * the device. For more information see the documentation in usbdrv/usbdrv.h.
 */
uchar   usbFunctionRead(uchar *data, uchar len)
{
    if(len > bytesRemaining)
        len = bytesRemaining;
    eeprom_read_block(data, (uchar *)0 + currentAddress, len);
    currentAddress += len;
    bytesRemaining -= len;
    return len;
}

/* usbFunctionWrite() is called when the host sends a chunk of data to the
 * device. For more information see the documentation in usbdrv/usbdrv.h.
 */
uchar   usbFunctionWrite(uchar *data, uchar len)
{
    if(bytesRemaining == 0)
        return 1;               /* end of transfer */
    if(len > bytesRemaining)
        len = bytesRemaining;
    eeprom_write_block(data, (uchar *)0 + currentAddress, len);
    currentAddress += len;
    bytesRemaining -= len;
    return bytesRemaining == 0; /* return 1 if this was the last chunk */
}

/* ------------------------------------------------------------------------- */

usbMsgLen_t usbFunctionSetup(uchar data[8])
{
usbRequest_t    *rq = (void *)data;

    if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){    /* HID class request */
        if(rq->bRequest == USBRQ_HID_GET_REPORT){  /* wValue: ReportType (highbyte), ReportID (lowbyte) */
            /* since we have only one report type, we can ignore the report-ID */
            bytesRemaining = 128;
            currentAddress = 0;
            return USB_NO_MSG;  /* use usbFunctionRead() to obtain data */
        }else if(rq->bRequest == USBRQ_HID_SET_REPORT){
            /* since we have only one report type, we can ignore the report-ID */
            bytesRemaining = 128;
            currentAddress = 0;
            return USB_NO_MSG;  /* use usbFunctionWrite() to receive data from host */
        }
    }else{
        /* ignore vendor type requests, we don't use any */
    }
    return 0;
}

uchar getInstX(struct diode instr) {
	return instr.loc&0x3;
}

uchar getInstY(struct diode instr) {
	return (instr.loc>>2)&0x3;
}

uchar getInstZ(struct diode instr) {
	return (instr.loc>>4)&0x3;
}

void setPin(uchar pinData, uchar on) {
	switch(pinData) {
		case A1:
		case A2:
		case A3:
		case A4:
		DIRA |= _BV(pinData&0x3);
		if(on)
			A_PORT |= _BV(pinData&0x3);
		else
			A_PORT &= ~_BV(pinData&0x3);
		break;
		case B1:
		case B2:
		case B3:
		case B4:
		DIRB |= _BV((pinData&0x3)+4);
		if(on)
			B_PORT |= _BV((pinData&0x3)+4);
		else
			B_PORT &= ~_BV((pinData&0x3)+4);
		break;
		case C1:
		case C2:
		case C3:
		case C4:
		DIRC |= _BV(pinData&0x03);
		if(on)
			C_PORT |= _BV(pinData&0x3);
		else
			C_PORT &= ~_BV(pinData&0x3);
		break;
		case D1:
		case D2:
		case D3:
		case D4:
		DIRD |= _BV((pinData&0x3)+4);
		if(on)
			D_PORT |= _BV((pinData&0x3)+4);
		else
			D_PORT &= ~_BV((pinData&0x3)+4);
		break;
	}
}

void setLED(struct diode LED) {
	if(LED.state) {
		uchar *location = LEDMAP[getInstX(LED)*16+getInstY(LED)*4+getInstZ(LED)];
		uchar rgb;
		/* Now we know which LED we're talking to, we can set the cathode */
		rgb = location[0];
		setPin((rgb>>0x04)&0x0F, 0);
		
		/* Set the anodes of all active LEDs */

		if(LED.state&REDLED) {
			setPin(rgb&0x0F, 1);
		}			
		if(LED.state&GREENLED) {
			rgb = location[1];
			setPin(rgb&0x0F, 1);
		}
		if(LED.state&BLUELED) {
			rgb = location[2];
			setPin(rgb&0x0F, 1);
		}
	}
	/* All LEDs off, clear the ports. This is done to reduce the maximum 
	 * brightness and extend the life of the LEDs and not stress the output
	 * pins on the MCU*/
	DDRC = 0x00;
	DDRD = 0x00;
	//PORTC = 0;
	//PORTD = 0;
}

void initCube() {
	framenumber=0;
}

void addDiode(uchar x, uchar y, uchar z, uchar state) {
	struct diode* temp = (struct diode*)malloc(sizeof(struct diode));
	if(temp) {
		temp->loc = (z<<4)|(y<<2)|x;
		temp->state = state;
		currentFrame->diodes = temp;
		currentFrame->numDiodes++;
	}
}

void timersInit(void) {
	/* This function sets up an internal timer so that we trigger an
	 * interrupt every 50ms. */
	TCCR1B |= _BV(CS10); // Select clock source - CLK_IO/256.
	OCR1AH = 0x0C;
	OCR1AL = 0x35; //3125
	TIMSK1 |= _BV(OCIE1A); // Interrupt on OC1A
}

ISR(TIMER1_COMPA_vect) {
	if(framenumber+1<animation.numFrames)
		framenumber++;
	else
		framenumber=0;
}

int main(void)
{
	unsigned char i;
	DDRC = 0x00; // Set all LED driving pins as input
	DDRD = 0x00;

    wdt_enable(WDTO_1S);
	initCube();
	//timersInit();
    usbInit();
    usbDeviceDisconnect();  /* enforce re-enumeration, do this while interrupts are disabled! */

#if 0
	while(1) {
		setPin(C4,0);
		setPin(B3,1);
		setPin(B4,1);
		DDRC=0;
		DDRD=0;
	}
#else
    i = 0;
	while(--i){             /* fake USB disconnect for > 250 ms */
	    wdt_reset();
	    _delay_ms(1);
    }

    usbDeviceConnect();
	sei();

uchar x,y,z,zz;
for(x=0;x<4;x++) {
	for(y=0;y<3;y++) {
		for(z=0;z<4;z++) {
			for(zz=0;zz<3;zz++) {
				addDiode(x,y,z,(1<<zz));
				framenumber++;
				animation.numFrames++;
			}				
		}
	}
	
}

    while(1)
    {
        wdt_reset();
		drawFrame(framenumber);
        usbPoll();
    }
#endif
}